Skip to content

03 AJAX 原理

知识点自测

  1. 以下哪个方法可以把 JS 数据类型转成 JSON 字符串类型?

    • A: JSON.stringify()
    • B: JSON.parse()
    答案
    • 选择 A
    • JSON.stringify() 方法用于将 JavaScript 对象转换为 JSON 字符串。
    • JSON.parse() 则用于将 JSON 字符串解析为 JavaScript 对象。
  2. 以下哪个方法,会延迟一段时间后,再执行函数体,并执行一次就停止?

    • A: setTimeout(函数体,毫秒值)
    • B: setInterval(函数体,毫秒值)
    答案
    • 选择 A
    • setTimeout 方法用于在指定的毫秒数后执行一次函数体。
    • setInterval 方法会每隔指定的毫秒数执行一次函数体,不停地重复执行,直到被取消。
    • 所以,如果想延迟一段时间后执行函数并且只执行一次,应该使用 setTimeout
  3. 下面代码 result 结果是多少?

    js
    let obj = {
      status: 240,
    };
    const result = obj.status >= 200 && obj.status < 300;
    • A: true
    • B: 大于
    • C: 240
    • D: false
    答案
    • 选 A
    • 在这个代码片段中,result 变量的值是根据 obj.status 的范围判断的,因为 obj.status 的值是 240,它同时满足 obj.status >= 200obj.status < 300 这两个条件,因此 result 的值是 true
  4. 下面代码运行结果是多少?

    js
    let result = 'http://www.baidu.com';
    result += '?a=10';
    result += '&b=20';
    • A: 'http://www.baidu.com'
    • B:'?a=10'
    • C:'&b=20'
    • D: 'http://www.baidu.com?a=10&b=20'
    答案
    • 选 D
    • 这段代码首先初始化 result'http://www.baidu.com',然后使用 += 运算符追加 '?a=10''&b=20',最终得到的字符串是 'http://www.baidu.com?a=10&b=20'
  5. 哪个事件能实时检测到输入框值的变化?

    • A:input 事件
    • B:change 事件
    答案
    • 选 A
    • input 事件能够实时检测到输入框值的变化,无论是用户通过键盘输入、粘贴、剪切等方式修改输入框的值,都会触发 input 事件。
    • change 事件在用户完成输入并移出输入框时触发,不会像 input 事件那样实时反映输入框的变化。

学习目标

  1. 了解原生 AJAX 语法 - XMLHttpRequest(XHR)
  2. 了解 Promise 的概念和使用
  3. 了解 axios 内部工作的大概过程(XHR + Promise)
  4. 案例 - 天气预报

XMLHttpRequest

  • XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest 在 AJAX 编程中被大量使用。
  • 尽管名称如此,XMLHttpRequest 可以用于获取任何类型的数据,而不仅仅是 XML。它甚至支持 HTTP 以外的协议(包括 file:// 和 FTP),尽管可能受到更多出于安全等原因的限制。

XHR 基础使用

  1. AJAX 是浏览器与服务器通信的技术,采用 XMLHttpRequest 对象相关代码

  2. axios 是对 XHR 相关代码进行了封装,让我们只关心传递的接口参数

  3. 学习 XHR 也是了解 axios 内部与服务器交互过程的真正原理

    0b8d4897-1fed-41b3-91f1-b1042f23e4f1

语法

js
const xhr = new XMLHttpRequest();
xhr.open('请求方法', '请求 url 网址');
xhr.addEventListener('loadend', () => {
  // 响应结果
  console.log(xhr.response);
});
xhr.send();

案例 - 获取省份列表数据 XMLHttpRequest

目标:使用 XMLHttpRequest 对象与服务器通信

  1. 创建 XMLHttpRequest 对象
  2. 配置请求方法和请求 url 地址
  3. 监听 loadend 事件,接收响应结果
  4. 发起请求
案例 - 获取省份列表数据 XMLHttpRequest
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>20 获取省份列表数据 XMLHttpRequest</title>
</head>

<body style="padding: 3em;">
  <p class="province-list"></p>
  <script>
    // - 1. 创建 XMLHttpRequest 对象
    // - 2. 配置请求方法和请求 url 地址
    // - 3. 监听 loadend 事件,接收响应结果
    // - 4. 发起请求

    // 1. 创建 XMLHttpRequest 对象
    const xhr = new XMLHttpRequest()

    // 2. 配置请求方法和请求 url 地址
    xhr.open('get', 'http://hmajax.itheima.net/api/province')

    // 3. 监听 loadend 事件,接收响应结果
    xhr.addEventListener('loadend', function () {
      console.log(xhr.response)
      console.log(JSON.parse(xhr.response).list)
      console.log(JSON.parse(xhr.response).list.join(' '))
      document.querySelector('.province-list').innerHTML = JSON.parse(xhr.response).list.join(' ')
    })

    // 4. 发起请求
    xhr.send()

  </script>
</body>

</html>

总结

  1. AJAX 原理是什么?

    • window 提供的 XMLHttpRequest
  2. 为什么学习 XHR?

    • 有更多与服务器数据通信方式
    • 了解 axios 内部原理
  3. XHR 使用步骤?

    • 创建 XHR 对象
    • 调用 open 方法,设置 url 和请求方法
    • 监听 loadend 事件,接收结果
    • 调用 send 方法,发起请求

XHR 查询参数

  1. 什么是查询参数:携带额外信息给服务器,返回匹配想要的数据
  2. 查询参数原理要携带的位置和语法:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2
  3. 所以,原生 XHR 需要自己在 url 后面携带查询参数字符串,没有 axios 帮助我们把 params 参数拼接到 url 字符串后面了

案例 - 获取甘肃省下属的城市列表

案例 - 获取甘肃省下属的城市列表
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>21 获取甘肃省下属的城市列表</title>
</head>

<body>
  <p class="province-city"></p>

  <script>
    // 目标:使用 XHR 携带查询参数,展示甘肃省下属的城市列表
    // api: http://hmajax.itheima.net/api/city

    // 1. 创建 XHR 对象
    const xhr = new XMLHttpRequest()

    // 2. 配置请求方法和请求 url 地址
    xhr.open('get', 'http://hmajax.itheima.net/api/city?pname=甘肃省')

    // 3. 监听 loadend 事件,接收响应结果
    xhr.addEventListener('loadend', () => {
      console.log(xhr.response)
      console.log(JSON.parse(xhr.response).list)
      document.querySelector('.province-city').innerHTML = JSON.parse(xhr.response).list.join(' ')
    })

    // 4. 发起请求
    xhr.send()
  </script>
</body>

</html>

总结

  1. XHR 如何携带查询参数?

    • 在调用 open 方法的时候,在 url? 后面按照指定格式拼接参数名和值
    js
    xhr.open('get', `http://hmajax.itheima.net/api/area?pname=${pname}&cname=${cname}}`);
  2. JS 对象如何转成查询参数格式字符串?

    • URLSearchParams 把参数对象转成 参数名=值&参数名=值 格式的字符串
    js
    const paramsObj = new URLSearchParams({ pname, cname });
    const queryString = paramsObj.toString();
    console.log(queryString);
    xhr.open('get', `http://hmajax.itheima.net/api/area?${queryString}`);

案例 - 地区查询

案例 - 地区查询
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>22 地区查询</title>
  <link rel="stylesheet" href="./css/bootstrap.min.css">

</head>

<body class="p-3">
  <div class="container">
    <h2>04 查询地区列表</h2>
    <form class="form-group row" id="editForm">
      <div class="mb-3 col">
        <label for="province" class="form-label">省份</label>
        <select class="form-select" id="province" name="province">
          <option value="北京" selected>北京</option>
        </select>
      </div>
      <div class="mb-3 col">
        <label for="city" class="form-label">城市</label>
        <select class="form-select" id="city" name="city">
          <option value="北京市" selected>北京市</option>
        </select>
      </div>
    </form>
    <button class="btn btn-primary sel-btn mb-3" type="button">查询</button>
    <div class="mt-3 result">
      <p>地区列表</p>
      <ul class="list-group">
        <li class="list-group-item">东城区</li>
      </ul>
    </div>
  </div>
  <script>
    // 1. 获取省份列表数据添加到省份下拉菜单中
    // 2. 为省份下拉菜单绑定 onchange 事件,当省份发生改变时,获取对应的城市列表数据添加到城市下拉菜单中
    // 3. 为查询按钮绑定点击事件,当点击查询按钮时,获取当前选中的省份和城市,发送请求获取对应的地区列表数据,展示到页面中

    // 1. 获取省份列表数据添加到省份下拉菜单中
    const xhr = new XMLHttpRequest();
    xhr.open('get', 'http://hmajax.itheima.net/api/province');
    xhr.addEventListener('loadend', () => {
      console.log(JSON.parse(xhr.responseText).list.join(' '));
      document.querySelector('#province').innerHTML = JSON.parse(xhr.responseText)
        .list.map((item) => `<option value="${item}">${item}</option>`)
        .join('');
    });
    xhr.send();

    // 2. 为省份下拉菜单绑定 onchange 事件,当省份发生改变时,获取对应的城市列表数据添加到城市下拉菜单中
    document.querySelector('#province').addEventListener('change', () => {
      const xhr = new XMLHttpRequest();
      xhr.open('get', `http://hmajax.itheima.net/api/city?pname=${this.value}`);
      xhr.addEventListener('loadend', () => {
        console.log(JSON.parse(xhr.responseText).list.join(' '));
        document.querySelector('#city').innerHTML = JSON.parse(xhr.responseText)
          .list.map((item) => `<option value="${item}">${item}</option>`)
          .join('');
      });
      xhr.send();
    });

    // 3. 为查询按钮绑定点击事件,当点击查询按钮时,获取当前选中的省份和城市,发送请求获取对应的地区列表数据,展示到页面中
    document.querySelector('.sel-btn').addEventListener('click', () => {
      // 收集省份和城市名字
      const pname = document.querySelector('#province').value;
      const cname = document.querySelector('#city').value;

      // const xhr = new XMLHttpRequest();
      // xhr.open('get', `http://hmajax.itheima.net/api/area?pname=${pname}&cname=${cname}}`);
      // xhr.addEventListener('loadend', () => {
      //   console.log(JSON.parse(xhr.responseText).list.join(' '));
      //   document.querySelector('.list-group').innerHTML = `${JSON.parse(xhr.responseText)
      //     .list.map((item) => `<li class="list-group-item">${item}</li>`)
      //     .join('')}`;
      // });
      // xhr.send();

      const xhr = new XMLHttpRequest();
      // xhr.open('get', `http://hmajax.itheima.net/api/area?pname=${pname}&cname=${cname}`)
      // 组织查询参数字符串
      const paramsObj = new URLSearchParams({ pname, cname });
      const queryString = paramsObj.toString();
      console.log(queryString);
      xhr.open('get', `http://hmajax.itheima.net/api/area?${queryString}`);

      xhr.addEventListener('loadend', () => {
        console.log(JSON.parse(xhr.responseText).list.join(' '));
        document.querySelector('.list-group').innerHTML = `${JSON.parse(xhr.responseText)
          .list.map((item) => `<li class="list-group-item">${item}</li>`)
          .join('')}`;
      });
      xhr.send();
    });

  </script>
</body>

</html>

XHR 数据提交

案例 - 注册账号

案例 - 注册账号
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>23 注册账号 (xhr 数据提交 )</title>
  <link rel="stylesheet" href="./css/bootstrap.min.css">
</head>

<body class="p-3 mb-2 d-flex justify-content-center align-items-center vh-100">

  <div class="container p-5 shadow" style="width: 500px; background-color: #f5f5f5; border-radius: 10px;">
    <div class="toast position-fixed top-0 start-50 translate-middle-x" role="alert" aria-live="assertive"
      aria-atomic="true" data-bs-delay="2000">
      <div class="toast-body">
        操作成功
      </div>
    </div>
    <h2>05 注册账号</h2>
    <form class="form-group mt-5">
      <div class="mb-3">
        <label for="exampleInputUsername" class="form-label">Username</label>
        <input type="text" class="form-control" id="username" name="username" placeholder="鸡你太美cxk"
          aria-describedby="usernameHelp">
        <div id="usernameHelp" class="form-text">中英文和数字组成,最少 8 位。</div>
      </div>
      <div class="mb-3">
        <label for="exampleInputPassword1" class="form-label">Password</label>
        <input type="password" class="form-control" id="password" name="password" placeholder="987654321"
          aria-describedby="passwordHelp">
        <div id="passwordHelp" class="form-text">最少 6 位。</div>
      </div>
    </form>
    <button type="submit" class="btn btn-primary mb-3">注册账号</button>
  </div>

  <script src="./js/bootstrap.min.js"></script>
  <script>
    const submitBtn = document.querySelector('button[type=submit]');

    submitBtn.addEventListener('click', () => {
      var username = document.querySelector('#username').value;
      var password = document.querySelector('#password').value;
      console.log(username, password);

      var xhr = new XMLHttpRequest();
      // axios.post("http://hmajax.itheima.net/api/register", { username, password });
      xhr.open('POST', 'http://hmajax.itheima.net/api/register');

      xhr.addEventListener('loadend', () => {
        console.log(xhr.responseText);
        console.log(JSON.parse(xhr.responseText).message);

        const toastDom = document.querySelector('.toast');
        const toast = new bootstrap.Toast(toastDom);
        // 设置 toast 的内容
        toastDom.querySelector('.toast-body').textContent = JSON.parse(xhr.responseText).message;
        toastDom.querySelector('.toast-body').style.backgroundColor = 'var(--bs-primary)';
        toastDom.querySelector('.toast-body').classList.add('p-3', 'rounded-3');
        // 显示提示框
        toast.show();
      });

      xhr.setRequestHeader('Content-Type', 'application/json');
      // const userObj = { username, password }
      // const userStr = JSON.stringify(userObj)
      // xhr.send(userStr)
      xhr.send(JSON.stringify({ username, password }));
    });

  </script>

</body>

</html>

总结

  1. XHR 如何提交请求体数据?

    • send 中携带请求体数据,要按照后端要求的内容类型携带
    js
    xhr.send(JSON.stringify({ username, password }));

Promise

认识 Promise

  1. 什么是 Promise?

    • Promise 对象用于表示一个异步操作的最终完成(或失败)及其结构值
    • 一个 Promise 是一个代理,它代表一个在创建 promise 时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值。
  2. Promise 的好处是什么?

    • 逻辑更清晰(成功或失败会关联后续的处理函数)

    • 了解 axios 函数内部运作的机制

      285df779-6026-4d76-9d26-86f3320f5797

    • 能解决回调函数地狱问题(后面会讲到),今天先来看下它的基础使用

Promise 语法

js
// 1. 创建 Promise 对象
const p = new Promise((resolve, reject) => {
  // 2. 执行异步任务 - 并传递结果
  // 成功调用:resolve(值) 触发 then() 执行
  // 失败调用:reject(值) 触发 catch() 执行
});
// 3. 接收结果
p.then((result) => {
  // 成功
}).catch((error) => {
  // 失败
});

示例 - 认识 Promise 状态

示例 - 认识 Promise 状态
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>24 认识 Promise 状态</title>
</head>

<body>
  <script>
    // 目标:认识 Promise 状态
    // 1. 创建 Promise 对象(pending-待定状态)
    // 2. 执行异步代码
    // 3. 获取结果

    // 1. 创建 Promise 对象(pending-待定状态)
    const promise = new Promise((resolve, reject) => {
      // Promise 对象创建时,这里的代码都会执行了
      // 2. 执行异步代码
      setTimeout(() => {
        // resolve() => 'fulfilled 状态 - 已兑现' => then()
        // resolve('成功的结果');
        resolve('模拟 AJAX 请求 - 成功结果');

        // reject() => 'rejected 状态 - 已拒绝' => catch()
        // reject('模拟 AJAX 请求 - 失败结果')
        reject(new Error('模拟 AJAX 请求 - 失败结果'));
      }, 1000);
    });
    console.log(promise);

    // 3. 获取结果
    promise
      .then((value) => {
        console.log('成功的结果:', value);
      })
      .catch((reason) => {
        console.log('失败的结果:', reason);
      });

  </script>
</body>

</html>

总结

  1. 什么是 Promise ?

    • 表示(管理)一个异步操作最终状态结果值的对象
  2. 为什么学习 Promise ?

    • 成功和失败状态,可以关联对应处理函数
    • 了解 axios 内部运作的原理
  3. Promise 使用步骤?

    • new Promise 对象执行异步任务。
    • resolve 关联 then 的回调函数传递成功结果。
    • reject 关联 catch 的回调函数传递失败结果。
    javascript
    // 1. 创建 Promise 对象(pending-待定状态)
    const promise = new Promise((resolve, reject) => {
      // 2. 执行异步代码
      setTimeout(() => {
        resolve('模拟 AJAX 请求 - 成功结果');
        reject(new Error('模拟 AJAX 请求 - 失败结果'));
      }, 1000);
    });
    console.log(promise);
    
    // 3. 获取结果
    promise
      .then((value) => {
        console.log('成功的结果:', value);
      })
      .catch((reason) => {
        console.log('失败的结果:', reason);
      });

Promise 的状态

  1. 为什么要了解 Promise 的三种状态?

    • 知道 Promise 对象如何关联的处理函数,以及代码的执行顺序
  2. Promise 有哪三种状态?

    每个 Promise 对象必定处于以下三种状态之一

    • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
    • 已兑现(fulfilled):意味着操作成功完成。
    • 已拒绝(rejected):意味着操作失败。

    状态的英文字符串,可以理解为 Promise 对象内的字符串标识符,用于判断什么时候调用哪一个处理函数

    一个待定的 Promise 最终状态可以是已兑现并返回一个值,或者是已拒绝并返回一个原因(错误)。当其中任意一种情况发生时,通过 Promise 的 then 方法串联的处理程序将被调用。如果绑定相应处理程序时 Promise 已经兑现或拒绝,这处理程序将被立即调用,因此在异步操作完成和绑定处理程序之间不存在竞态条件。

  3. Promise 的状态改变有什么用:调用对应函数,改变 Promise 对象状态后,内部触发对应回调函数传参并执行

    f9669e19-8543-4315-91ff-38176ba006d0

  4. 注意:每个 Promise 对象一旦被兑现/拒绝,那就是已敲定了,状态无法再被改变

案例 - 使用 Promise 和 XHR 获取省份列表

  1. 创建 Promise 对象

  2. 执行 XHR 异步代码,获取省份列表数据

    • 响应状态码在大于等于 200 并且小于 300 的范围是成功的
  3. 关联成功或失败回调函数,做后续的处理

    错误情况:用地址错了 404 演示

案例 - 使用 Promise 和 XHR 获取省份列表
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>25 使用 Promise 和 XHR 获取省份列表</title>
</head>

<body style="padding: 3em;">
  <p class="province-list"></p>
  <script>
    /**
     * 目标:使用 Promise 管理 XHR 请求省份列表
     *  1. 创建 Promise 对象
     *  2. 执行 XHR 异步代码,获取省份列表
     *  3. 关联成功或失败函数,做后续处理
    */

    // 1. 创建 Promise 对象
    const promise = new Promise((resolve, reject) => {
      // 2. 执行 XHR 异步代码,获取省份列表
      const xhr = new XMLHttpRequest()
      xhr.open('get', 'http://hmajax.itheima.net/api/province')
      xhr.addEventListener('loadend', () => {
        // xhr 如何判断响应成功还是失败的?
        // 2xx 开头的都是成功响应状态码
        // if (xhr.status >= 200 && xhr.status < 300) {
        if (/^2\d{2}$/.test(xhr.status)) {
          // 成功
          resolve(JSON.parse(xhr.response))
        } else {
          // 失败
          reject(new Error(xhr.response))
        }
      })
      xhr.send()
    })

    // 3. 关联成功或失败函数,做后续处理
    promise
      .then((res) => {
        console.log('成功的结果:', res.list)
        document.querySelector('.province-list').innerHTML = res.list.join(' ')
      })
      .catch((err) => {
        console.log('失败的结果:', err)
        document.querySelector('.province-list').innerHTML = err.message;
      })
  </script>
</body>

</html>

案例 - 简易 axios 函数 获取省份列表

  1. 基于 PromiseXHR 封装 myAxios 函数

  2. 核心语法

    js
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        // XHR 请求
        // 调用成功/失败的处理程序
      });
    }
    
    myAxios({
      url: '目标资源地址',
    })
      .then((result) => {})
      .catch((error) => {});
  3. 步骤:

    • 定义 myAxios 函数,接收配置对象,返回 Promise 对象
    • 发起 XHR 请求,默认请求方法为 GET
    • 调用成功/失败的处理程序
    • 使用 myAxios 函数,获取省份列表展示
案例 - 简易 axios 函数 获取省份列表
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>26 简易 axios 函数 获取省份列表</title>
</head>

<body style="padding: 3em;">
  <p class="province-list"></p>
  <script>
    /**
     * 目标:封装_简易 axios 函数_获取省份列表
     *  1. 定义 myAxios 函数,接收配置对象,返回 Promise 对象
     *  2. 发起 XHR 请求,默认请求方法为 GET
     *  3. 调用成功/失败的处理程序
     *  4. 使用 myAxios 函数,获取省份列表展示
    */

    // 1. 定义 myAxios 函数,接收配置对象,返回 Promise 对象
    function myAxios(config) {

      // 返回 Promise 对象
      return new Promise((resolve, reject) => {

        // 2. 发起 XHR 请求,默认请求方法为 GET
        const xhr = new XMLHttpRequest()

        // 判断是否传入请求方法
        // if (config.method) {
        //   xhr.open(config.method, config.url)
        // } else {
        //   xhr.open('get', config.url)
        // }
        xhr.open(config.method || 'get', config.url)

        // 判断是否传入请求头
        // if (config.headers) {
        //   for (let key in config.headers) {
        //     xhr.setRequestHeader(key, config.headers[key])
        //   }
        // }

        // 3. 调用成功/失败的处理程序
        xhr.addEventListener('loadend', () => {

          // if (xhr.status >= 200 && xhr.status < 300) {
          if (/^2\d{2}$/.test(xhr.status)) {
            // 成功
            resolve(JSON.parse(xhr.response))
          } else {
            // 失败
            reject(new Error(xhr.response))
          }
        })
        xhr.send()
      })
    }

    // 4. 使用 myAxios 函数,获取省份列表展示
    myAxios({
      url: 'http://hmajax.itheima.net/api/province'
    }).then((res) => {
      console.log(res)
      document.querySelector('.province-list').innerHTML = res.list.join(' ')
    }).catch((err) => {
      console.log(err)
      document.querySelector('.province-list').innerHTML = err.message
    })
  </script>
</body>

</html>

案例 - 简易 axios 函数 获取陕西省的城市列表

修改 myAxios 函数支持传递查询参数,获取陕西省的城市列表。

  1. 判断有 params 选项,携带查询参数
  2. 使用 myAxios 函数,获取城市列表
案例 - 简易 axios 函数 获取陕西省的城市列表
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>27 简易 axios 函数 获取陕西省的城市列表</title>
</head>

<body style="padding: 3em;">
  <p class="province-city"></p>
  <script>
    /**
     * 1. 判断有 `params` 选项,携带查询参数
     * 2. 使用 `myAxios` 函数,获取城市列表展示
    */

    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        // 判断有 `params` 选项,携带查询参数
        if (config.params) {
          var params = ''
          // 遍历 `params` 对象,拼接查询参数字符串
          for (var key in config.params) {
            params += `${key}=${config.params[key]}&`
          }
          params = params.slice(0, -1) // 去掉最后一个 `&`
          config.url += '?' + params
        }
        console.log(config.url)
        xhr.open(config.method || 'get', config.url)
        xhr.addEventListener('loadend', () => {
          if (/^2\d{2}$/.test(xhr.status)) {
            console.log(JSON.parse(xhr.response))
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        xhr.send()
      })
    }

    // 使用 `myAxios` 函数,获取城市列表展示
    myAxios({
      url: 'http://hmajax.itheima.net/api/city',
      params: {
        pname: '陕西省'
      }
    }).then((res) => {
      document.querySelector('.province-city').innerHTML = res.list.join(' ')
    }).catch((err) => {
      console.log(err.message)
      document.querySelector('.province-city').innerHTML = err.message
    })
  </script>
</body>

</html>

案例 - 简易 axios 函数 获取地区列表

案例 - 简易 axios 函数 获取地区列表
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>28 简易 axios 函数 获取地区列表</title>
  <link rel="stylesheet" href="./css/bootstrap.min.css">

</head>

<body class="p-3">
  <div class="container">
    <h2>04 查询地区列表</h2>
    <form class="form-group row" id="editForm">
      <div class="mb-3 col">
        <label for="province" class="form-label">省份</label>
        <select class="form-select" id="province" name="province">
          <option value="北京" selected>北京</option>
        </select>
      </div>
      <div class="mb-3 col">
        <label for="city" class="form-label">城市</label>
        <select class="form-select" id="city" name="city">
          <option value="北京市" selected>北京市</option>
        </select>
      </div>
    </form>
    <button class="btn btn-primary sel-btn mb-3" type="button">查询</button>
    <div class="mt-3 result">
      <p>地区列表</p>
      <ul class="list-group">
        <li class="list-group-item">东城区</li>
      </ul>
    </div>
  </div>
  <script>
    /**
     * 目标:封装_简易 axios 函数_获取地区列表
     *  1. 判断有 params 选项,携带查询参数
     *  2. 使用 URLSearchParams 转换,并携带到 url 上
     *  3. 使用 myAxios 函数,获取地区列表
    */
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        if (config.params) {
          // var params = ''
          // for (var key in config.params) {
          //   params += `${key}=${config.params[key]}&`
          // }
          // params = params.slice(0, -1)

          // 使用 URLSearchParams 转换,并携带到 url 上
          const paramsObj = new URLSearchParams(config.params);
          const params = paramsObj.toString();

          config.url += '?' + params;
        }
        xhr.open(config.method || 'get', config.url);
        xhr.addEventListener('loadend', () => {
          if (/^2\d{2}$/.test(xhr.status)) {
            resolve(JSON.parse(xhr.response));
          } else {
            reject(new Error(xhr.response));
          }
        });
        xhr.send();
      });
    }

    const province = document.querySelector('#province');
    const city = document.querySelector('#city');
    // 获取省份列表数据添加到省份下拉菜单中
    myAxios({
      url: 'http://hmajax.itheima.net/api/province',
    })
      .then((res) => {
        province.innerHTML = res.list.map((item) => `<option value="${item}">${item}</option>`).join('');
      })
      .catch((err) => {
        console.log(err.message);
      });

    // 为省份下拉菜单绑定 onchange 事件,当省份发生改变时,获取对应的城市列表数据添加到城市下拉菜单中
    province.addEventListener('change', () => {
      myAxios({
        url: `http://hmajax.itheima.net/api/city`,
        params: {
          pname: province.value,
        },
      })
        .then((res) => {
          city.innerHTML = res.list.map((item) => `<option value="${item}">${item}</option>`).join('');
        })
        .catch((err) => {
          console.log(err.message);
        });
    });

    // 为查询按钮绑定点击事件,当点击查询按钮时,获取当前选中的省份和城市,发送请求获取对应的地区列表数据,展示到页面中
    document.querySelector('.sel-btn').addEventListener('click', () => {
      myAxios({
        url: `http://hmajax.itheima.net/api/area`,
        params: {
          pname: province.value,
          cname: city.value,
        },
      })
        .then((res) => {
          document.querySelector('.list-group').innerHTML = res.list
            .map((item) => `<li class="list-group-item">${item}</li>`)
            .join('');
        })
        .catch((err) => {
          console.log(err.message);
        });
    });

  </script>
</body>

</html>

案例 - 简易 axios 函数 注册用户

案例 - 简易 axios 函数 注册用户
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>29 简易 axios 函数 注册账号</title>
  <link rel="stylesheet" href="./css/bootstrap.min.css">
</head>

<body class="p-3 mb-2 d-flex justify-content-center align-items-center vh-100">

  <div class="container p-5 shadow" style="width: 500px; background-color: #f5f5f5; border-radius: 10px;">
    <div class="toast position-fixed top-0 start-50 translate-middle-x" role="alert" aria-live="assertive"
      aria-atomic="true" data-bs-delay="2000">
      <div class="toast-body">
        操作成功
      </div>
    </div>
    <h2>05 注册账号</h2>
    <form class="form-group mt-5">
      <div class="mb-3">
        <label for="exampleInputUsername" class="form-label">Username</label>
        <input type="text" class="form-control" id="username" name="username" placeholder="鸡你太美cxk"
          aria-describedby="usernameHelp">
        <div id="usernameHelp" class="form-text">中英文和数字组成,最少 8 位。</div>
      </div>
      <div class="mb-3">
        <label for="exampleInputPassword1" class="form-label">Password</label>
        <input type="password" class="form-control" id="password" name="password" placeholder="987654321"
          aria-describedby="passwordHelp">
        <div id="passwordHelp" class="form-text">最少 6 位。</div>
      </div>
    </form>
    <button type="submit" class="btn btn-primary mb-3">注册账号</button>
  </div>

  <script src="./js/bootstrap.min.js"></script>
  <script>
    /**
     * 目标:封装_简易 axios 函数_注册用户
     *  1. 判断有 data 选项,携带请求体
     *  2. 转换数据类型,在 send 中发送
     *  3. 使用 myAxios 函数,完成注册用户
    */
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        if (config.params) {
          const paramsObj = new URLSearchParams(config.params);
          const params = paramsObj.toString();
          config.url += '?' + params;
        }
        xhr.open(config.method || 'get', config.url);
        xhr.addEventListener('loadend', () => {
          if (/^2\d{2}$/.test(xhr.status)) {
            resolve(JSON.parse(xhr.response));
          } else {
            reject(new Error(xhr.response));
          }
        });

        // 判断有 data 选项,携带请求体
        if (config.data) {
          // 转换数据类型,在 send 中发送
          xhr.setRequestHeader('Content-Type', 'application/json');
          xhr.send(JSON.stringify(config.data));
        } else {
          xhr.send();
        }
      });
    }

    const submitBtn = document.querySelector('button[type=submit]');
    submitBtn.addEventListener('click', () => {
      const username = document.querySelector('#username').value;
      const password = document.querySelector('#password').value;
      console.log(username, password);

      const toastDom = document.querySelector('.toast');
      const toast = new bootstrap.Toast(toastDom);
      toastDom.querySelector('.toast-body').style.backgroundColor = 'var(--bs-primary)';
      toastDom.querySelector('.toast-body').classList.add('p-3', 'rounded-3');

      myAxios({
        method: 'post',
        url: 'http://hmajax.itheima.net/api/register',
        data: {
          username,
          password,
        },
      })
        .then((res) => {
          console.log(res);
          toastDom.querySelector('.toast-body').textContent = res.message;
          toast.show();
        })
        .catch((err) => {
          console.log(err.message);
          toastDom.querySelector('.toast-body').textContent = JSON.parse(err.message).message;
          toast.show();
        });
    });

  </script>

</body>

</html>

总结

  1. Promise 对象有哪 3 种状态?

    • 待定 pending
    • 已兑现 fulfilled
    • 已拒绝 rejected
  2. Promise 状态有什么用?

    • 状态改变后,如何关联处理函数
  3. AJAX 如何判断是否请求响应成功了?

    • 响应状态码在大于等于 200 并且小于 300 的范围是成功的
    js
    // if (xhr.status >= 200 && xhr.status < 300) {
    if (/^2\d{2}$/.test(xhr.status)) {
      // 成功
      resolve(JSON.parse(xhr.response));
    } else {
      // 失败
      reject(new Error(xhr.response));
    }
  4. 自己封装的 myAxios 如何设置默认请求方法 GET?

    • config.method 判断有值就用,无值用 GET 方法
    js
    // 判断是否传入请求方法
    // if (config.method) {
    //   xhr.open(config.method, config.url)
    // } else {
    //   xhr.open('get', config.url)
    // }
    xhr.open(config.method || 'get', config.url);
  5. 外面传入查询参数对象,myAxios 函数内如何转查询参数字符串?

    • 使用 URLSearchParams 对象转换
    js
    if (config.params) {
      // var params = ''
      // for (var key in config.params) {
      //   params += `${key}=${config.params[key]}&`
      // }
      // params = params.slice(0, -1)
    
      // 使用 URLSearchParams 转换,并携带到 url 上
      const paramsObj = new URLSearchParams(config.params);
      const params = paramsObj.toString();
    
      config.url += '?' + params;
    }
  6. 外面传入 data 选项,myAxios 函数内如何携带请求体参数?

    • 判断外面传入了这个属性,自己转成 JSON 字符串并设置请求头并在 send 方法中携带
    js
    // 判断有 data 选项,携带请求体
    if (config.data) {
      // 转换数据类型,在 send 中发送
      xhr.setRequestHeader('Content-Type', 'application/json');
      xhr.send(JSON.stringify(config.data));
    } else {
      xhr.send();
    }

案例 - 天气预报

根据关键字,展示匹配的城市列表

默认数据

855a3a26-9c64-4ba4-9716-4f45a51b63e8

默认数据 - 获取城市天气信息
js
/**
 * @description: 获取天气预报
 * @param {string } cityCode
 *
 * api: http://hmajax.itheima.net/api/weather (GET)
 */
function getWeather(cityCode) {
  myAxios({
    url: 'http://hmajax.itheima.net/api/weather',
    params: {
      city: cityCode,
    },
  }).then((res) => {
    console.log(res.data);
    // 打印出对象的属性名
    console.log(Object.keys(res.data));

    const {
      date,
      dateLunar,
      area,
      temperature,
      weather,
      weatherImg,
      windPower,
      windDirection,
      psPm25Level,
      psPm25,
      todayWeather,
      dayForecast,
    } = res.data;

    function setElement(selector, value, isImage = false) {
      const element = document.querySelector(selector);
      if (isImage) {
        element.src = value;
      } else {
        element.innerHTML = value;
      }
    }

    setElement('span.dateShort', date);
    setElement('span.dateLunar', dateLunar);
    setElement('span.area', area);
    setElement('span.temperature', temperature);
    setElement('span.psPm25', psPm25);
    setElement('span.psPm25Level', psPm25Level);
    setElement('img.weatherImg', weatherImg, true);
    setElement('span.weather', weather);
    setElement('span.windDirection', windDirection);
    setElement('span.windPower', windPower);

    const {
      humidity,
      sunriseTime,
      sunsetTime,
      temDay,
      temNight,
      ultraviolet,
      weather: todayWeatherWeather,
    } = todayWeather;

    setElement('.range span.weather', todayWeatherWeather);
    setElement('span.temNight', temNight);
    setElement('span.temDay', temDay);
    setElement('span.ultraviolet', ultraviolet);
    setElement('span.humidity', humidity);
    setElement('span.sunriseTime', sunriseTime);
    setElement('span.sunsetTime', sunsetTime);

    // 1.3 未来七天天气预报
    document.querySelector('.week-wrap').innerHTML = dayForecast
      .map(
        ({ dateFormat, date, weatherImg, weather, temNight, temDay, windDirection, windPower }) => `
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">${dateFormat}</span>
            <span class="date">${date}</span>
          </div>
          <img src="${weatherImg}" alt="" class="weatherImg">
          <span class="weather">${weather}</span>
          <div class="temp">
            <span class="temNight">${temNight}</span>-
            <span class="temDay">${temDay}</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">${windDirection}</span>
            <span class="windPower">${windPower}</span>
          </div>
        </li>
      `,
      )
      .join('');
  });
}
javascript
// 页面加载后默认展示北京市的天气信息
// 北京市的城市 code 为 110100
getWeather('110100');

搜索城市列表

8dc3ca94-d576-4ee9-8334-b69ccd5f3e8d

搜索城市列表 - 获取城市列表
javascript
/**
 * 目标 2:搜索城市列表
 *  2.1 绑定 input 事件,获取关键字
 *  2.2 获取展示城市列表数据
 */
document.querySelector('.search-city').addEventListener('input', (e) => {
  console.log(e.target.value);

  myAxios({
    url: 'http://hmajax.itheima.net/api/weather/city',
    params: {
      city: e.target.value,
    },
  })
    .then((res) => {
      console.log(res);
      // 渲染城市列表
      document.querySelector('.search-list').innerHTML = res.data
        .map((item) => `<li class="city-item" data-code="${item.code}">${item.name}</li>`)
        .join('');
    })
    .catch((err) => {
      // console.dir 可以打印详细信息:对象的属性名
      console.dir(err.message);
    });
});

展示城市天气

d3a378f0-65fe-40a5-8235-261bfffd1599

展示城市天气 - 切换城市天气
javascript
/**
 * 目标 3:切换城市天气
 *  3.1 绑定城市点击事件,获取城市 code 值
 *  3.2 调用获取并展示天气的函数
 */
document.querySelector('.search-list').addEventListener('click', (e) => {
  console.log(e.target);
  if (e.target.classList.contains('city-item')) {
    // const cityCode = e.target.dataset.code;
    const cityCode = e.target.getAttribute('data-code');
    console.log(cityCode);
    getWeather(cityCode);
  }
});

总结

  1. 监听输入框实时改变的事件是什么?

    • input 事件

今日重点

  1. 了解 AJAX 原理之 XMLHttpRequest(XHR)相关语法

    js
    // 创建 XMLHttpRequest 对象
    const xhr = new XMLHttpRequest();
    
    // 组织查询参数字符串
    const paramsObj = new URLSearchParams({ pname, cname });
    const queryString = paramsObj.toString();
    
    // 配置请求方法和请求 url 地址
    xhr.open('get', `http://hmajax.itheima.net/api/area?${queryString}`);
    
    // 设置 HTTP 请求头(可选)
    xhr.setRequestHeader('Content-Type', 'application/json');
    
    // 处理状态变化事件
    // - onreadystatechange:事件处理属性,指定在 readyState 属性改变时触发的函数
    // - readyState:表示 XMLHttpRequest 的状态,有 5 个值(0-4),分别对应不同的状态
    // - status:表示 HTTP 请求的状态码,例如 200 表示成功
    // - responseText 和 responseXML:包含响应的文本或 XML 数据
    xhr.addEventListener('loadend', () => {
      // 响应结果
      console.log(xhr.response);
    });
    
    // 发送 HTTP 请求
    xhr.send();
    // 对于 POST 请求,可以在 send 方法中传递请求体参数
    // xhr.send('param1=value1&param2=value2');
    // xhr.send(JSON.stringify({ username, password }));
  2. 了解 Promise 的作用和三种状态

    • Promise 是 JavaScript 中用于处理异步操作的对象。它提供了更优雅和可读性更高的方式来处理异步代码,避免了回调地狱(callback hell)。

    • Promise 的作用:

      • 更清晰的异步代码: 使用 Promise,可以通过 thencatch 方法来组织和处理异步代码,使代码更具可读性和可维护性。
      • 避免回调地狱: Promise 可以链式调用,避免了回调地狱,使代码结构更加清晰。
      • 处理异步操作的结果: Promise 对象有三种状态,可以处理异步操作的成功和失败情况,并且能够在状态改变时执行相应的操作。
    • Promise 的三种状态:

      • Pending(进行中): 初始状态,表示异步操作正在进行中。此时 Promise 对象还未成功或失败。
      • Fulfilled(已成功): 表示异步操作成功完成,并返回一个结果值。此时 Promise 对象的状态从 Pending 变为 Fulfilled
      • Rejected(已失败): 表示异步操作失败,并返回一个错误原因。此时 Promise 对象的状态从 Pending 变为 Rejected
    • Promise 的基本用法:

      js
      const promise = new Promise((resolve, reject) => {
        // 异步操作,成功时调用 resolve,失败时调用 reject
        setTimeout(() => {
          resolve('成功结果');
          // 或者
          // reject(new Error("失败原因"));
        }, 1000);
      });
      
      promise
        .then((res) => {
          // 处理成功的情况
          console.log(res);
        })
        .catch((err) => {
          // 处理失败的情况
          console.error(err);
        });

      在上面的例子中,then 方法用于注册在 Promise 对象状态变为 Fulfilled 时执行的函数,catch 方法用于注册在状态变为 Rejected 时执行的函数。

  3. 了解 axios 内部运作的过程

    • Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。它允许在浏览器中发送异步 HTTP 请求,并提供了更方便的 API。

    • Axios 内部运作的主要过程:

      • 创建 Axios 实例: 在使用 Axios 之前,通常需要创建一个 Axios 实例,以便配置一些全局参数,例如基础 URL、请求头等。

        js
        const axiosInstance = axios.create({
          baseURL: 'https://api.example.com',
          headers: {
            'Content-Type': 'application/json',
          },
        });
      • 发送请求: 使用创建的 Axios 实例可以发送各种类型的 HTTP 请求,例如 GETPOSTPUTDELETE 等。

        js
        axiosInstance
          .get('/data')
          .then((response) => {
            // 处理成功的响应
            console.log(response.data);
          })
          .catch((error) => {
            // 处理失败的响应
            console.error(error);
          });
      • 封装请求: Axios 封装了底层的 XMLHTTPRequest 或者 Node.js 中的 http 模块,以处理底层的网络通信。它隐藏了大部分底层实现,使开发者能够更专注于业务逻辑。

      • Promise 封装: Axios 使用 Promise 对象来处理异步操作。每个 Axios 请求返回一个 Promise 对象,允许通过 thencatch 方法处理成功和失败的响应。

        js
        axiosInstance.interceptors.request.use(
          (config) => {
            // 在请求被发送之前做些什么
            return config;
          },
          (error) => {
            // 对请求错误做些什么
            return Promise.reject(error);
          },
        );
        
        axiosInstance.interceptors.response.use(
          (response) => {
            // 对响应数据做些什么
            return response;
          },
          (error) => {
            // 对响应错误做些什么
            return Promise.reject(error);
          },
        );
      • 拦截器(Interceptors): Axios 提供了拦截器,允许在请求或响应被 thencatch 处理之前拦截它们。这使得可以在请求发送之前或响应处理之后执行一些额外的逻辑。

        js
        axiosInstance.interceptors.request.use(
          (config) => {
            // 在请求被发送之前做些什么
            return config;
          },
          (error) => {
            // 对请求错误做些什么
            return Promise.reject(error);
          },
        );
        
        axiosInstance.interceptors.response.use(
          (response) => {
            // 对响应数据做些什么
            return response;
          },
          (error) => {
            // 对响应错误做些什么
            return Promise.reject(error);
          },
        );
  4. 完成案例 - 天气预报

    html
    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="stylesheet" href="./css/reset.css">
      <link rel="stylesheet" href="./css/index.css">
      <title>案例_天气预报</title>
    </head>
    
    <body>
      <div class="container">
        <!-- 顶部 -->
        <div class="top-box">
          <div class="title">
            <span class="dateShort">10 月 28 日</span>
            <span class="calendar">农历&nbsp;
              <span class="dateLunar">十月初四</span>
            </span>
          </div>
          <div class="search-box">
            <div class="location">
              <img src="./imgs/定位.png" alt="">
              <span class="area">城市名</span>
            </div>
            <!-- 搜索框 + 搜索列表 -->
            <div class="search">
              <input type="text" class="search-city" placeholder="搜索城市">
              <ul class="search-list">
                <li class="city-item">北京市</li>
              </ul>
            </div>
          </div>
        </div>
        <!-- 当前天气 -->
        <div class="weather-box">
          <div class="tem-box">
            <span class="temp">
              <span class="temperature">12</span>
              <span>°</span>
            </span>
          </div>
          <div class="climate-box">
            <div class="air">
              <span class="psPm25">55</span>
              <span class="psPm25Level">良</span>
            </div>
            <ul class="weather-list">
              <li>
                <img src="./imgs/小雨-line.png" class="weatherImg" alt="">
                <span class="weather">多云</span>
              </li>
              <li class="windDirection">东南风</li>
              <li class="windPower">2 级</li>
            </ul>
          </div>
        </div>
        <div class="today-weather">
          <div class="range-box">
            <span>今天:</span>
            <span class="range">
              <span class="weather">晴</span>
              <span class="temNight">9</span>
              <span>-</span>
              <span class="temDay">14</span>
              <span>℃</span>
            </span>
          </div>
          <ul class="sun-list">
            <li>
              <span>紫外线</span>
              <span class="ultraviolet">强</span>
            </li>
            <li>
              <span>湿度</span>
              <span class="humidity">53</span>%
            </li>
            <li>
              <span>日出</span>
              <span class="sunriseTime">06:38</span>
            </li>
            <li>
              <span>日落</span>
              <span class="sunsetTime">17:18</span>
            </li>
          </ul>
        </div>
        <!-- 周天气预报 -->
        <div class="week-weather-box">
          <div class="title">7 日内天气预报</div>
          <ul class="week-wrap">
            <li class="item">
              <div class="date-box">
                <span class="dateFormat">今天</span>
                <span class="date">10 月 28 日</span>
              </div>
              <img src="./imgs/多云.png" alt="" class="weatherImg">
              <span class="weather">多云</span>
              <div class="temp">
                <span class="temNight">12</span>-
                <span class="temDay">12</span>
                <span>℃</span>
              </div>
              <div class="wind">
                <span class="windDirection">东南风</span>
                <span class="windPower">&lt;3 级</span>
              </div>
            </li>
            <li class="item">
              <div class="date-box">
                <span class="dateFormat">今天</span>
                <span class="date">10 月 28 日</span>
              </div>
              <img src="./imgs/多云.png" alt="" class="weatherImg">
              <span class="weather">多云</span>
              <div class="temp">
                <span class="temDay">12</span>-
                <span class="temNight">12</span>
                <span>℃</span>
              </div>
              <div class="wind">
                <span class="windDirection">东南风</span>
                <span class="windPower">&lt;3 级</span>
              </div>
            </li>
            <li class="item">
              <div class="date-box">
                <span class="dateFormat">今天</span>
                <span class="date">10 月 28 日</span>
              </div>
              <img src="./imgs/多云.png" alt="" class="weatherImg">
              <span class="weather">多云</span>
              <div class="temp">
                <span class="temDay">12</span>-
                <span class="temNight">12</span>
                <span>℃</span>
              </div>
              <div class="wind">
                <span class="windDirection">东南风</span>
                <span class="windPower">&lt;3 级</span>
              </div>
            </li>
            <li class="item">
              <div class="date-box">
                <span class="dateFormat">今天</span>
                <span class="date">10 月 28 日</span>
              </div>
              <img src="./imgs/多云.png" alt="" class="weatherImg">
              <span class="weather">多云</span>
              <div class="temp">
                <span class="temDay">12</span>-
                <span class="temNight">12</span>
                <span>℃</span>
              </div>
              <div class="wind">
                <span class="windDirection">东南风</span>
                <span class="windPower">&lt;3 级</span>
              </div>
            </li>
            <li class="item">
              <div class="date-box">
                <span class="dateFormat">今天</span>
                <span class="date">10 月 28 日</span>
              </div>
              <img src="./imgs/多云.png" alt="" class="weatherImg">
              <span class="weather">多云</span>
              <div class="temp">
                <span class="temDay">12</span>-
                <span class="temNight">12</span>
                <span>℃</span>
              </div>
              <div class="wind">
                <span class="windDirection">东南风</span>
                <span class="windPower">&lt;3 级</span>
              </div>
            </li>
            <li class="item">
              <div class="date-box">
                <span class="dateFormat">今天</span>
                <span class="date">10 月 28 日</span>
              </div>
              <img src="./imgs/多云.png" alt="" class="weatherImg">
              <span class="weather">多云</span>
              <div class="temp">
                <span class="temDay">12</span>-
                <span class="temNight">12</span>
                <span>℃</span>
              </div>
              <div class="wind">
                <span class="windDirection">东南风</span>
                <span class="windPower">&lt;3 级</span>
              </div>
            </li>
            <li class="item">
              <div class="date-box">
                <span class="dateFormat">今天</span>
                <span class="date">10 月 28 日</span>
              </div>
              <img src="./imgs/多云.png" alt="" class="weatherImg">
              <span class="weather">多云</span>
              <div class="temp">
                <span class="temDay">12</span>-
                <span class="temNight">12</span>
                <span>℃</span>
              </div>
              <div class="wind">
                <span class="windDirection">东南风</span>
                <span class="windPower">&lt;3 级</span>
              </div>
            </li>
          </ul>
        </div>
      </div>
      <!-- 自己封装 myAxios 函数 -->
      <script src="./js/my-axios.js"></script>
      <!-- 搜索框 + 下拉菜单出现逻辑 -->
      <script src="./js/search.js"></script>
      <!-- 获取城市天气信息 -->
      <script src="./js/getWeather.js"></script>
      <!-- 核心 js -->
      <script src="./js/index.js"></script>
    </body>
    
    </html>
    js
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        if (config.params) {
          const paramsObj = new URLSearchParams(config.params);
          const queryString = paramsObj.toString();
          config.url += `?${queryString}`;
        }
        xhr.open(config.method || 'GET', config.url);
        xhr.addEventListener('loadend', () => {
          if (/^2\d{2}$/.test(xhr.status)) {
            resolve(JSON.parse(xhr.response));
          } else {
            reject(new Error(xhr.response));
          }
        });
        if (config.data) {
          xhr.setRequestHeader('Content-Type', 'application/json');
          xhr.send(JSON.stringify(config.data));
        } else {
          xhr.send();
        }
      });
    }
    js
    /**
     * @description: 获取天气预报
     * @param {string } cityCode
     *
     * api: http://hmajax.itheima.net/api/weather (GET)
     */
    function getWeather(cityCode) {
      myAxios({
        url: 'http://hmajax.itheima.net/api/weather',
        params: {
          city: cityCode,
        },
      }).then((res) => {
        console.log(res.data);
        // 打印出对象的属性名
        console.log(Object.keys(res.data));
    
        const {
          date,
          dateLunar,
          area,
          temperature,
          weather,
          weatherImg,
          windPower,
          windDirection,
          psPm25Level,
          psPm25,
          todayWeather,
          dayForecast,
        } = res.data;
    
        function setElement(selector, value, isImage = false) {
          const element = document.querySelector(selector);
          if (isImage) {
            element.src = value;
          } else {
            element.innerHTML = value;
          }
        }
    
        setElement('span.dateShort', date);
        setElement('span.dateLunar', dateLunar);
        setElement('span.area', area);
        setElement('span.temperature', temperature);
        setElement('span.psPm25', psPm25);
        setElement('span.psPm25Level', psPm25Level);
        setElement('img.weatherImg', weatherImg, true);
        setElement('span.weather', weather);
        setElement('span.windDirection', windDirection);
        setElement('span.windPower', windPower);
    
        const {
          humidity,
          sunriseTime,
          sunsetTime,
          temDay,
          temNight,
          ultraviolet,
          weather: todayWeatherWeather,
        } = todayWeather;
    
        setElement('.range span.weather', todayWeatherWeather);
        setElement('span.temNight', temNight);
        setElement('span.temDay', temDay);
        setElement('span.ultraviolet', ultraviolet);
        setElement('span.humidity', humidity);
        setElement('span.sunriseTime', sunriseTime);
        setElement('span.sunsetTime', sunsetTime);
    
        // 1.3 未来七天天气预报
        document.querySelector('.week-wrap').innerHTML = dayForecast
          .map(
            ({ dateFormat, date, weatherImg, weather, temNight, temDay, windDirection, windPower }) => `
            <li class="item">
              <div class="date-box">
                <span class="dateFormat">${dateFormat}</span>
                <span class="date">${date}</span>
              </div>
              <img src="${weatherImg}" alt="" class="weatherImg">
              <span class="weather">${weather}</span>
              <div class="temp">
                <span class="temNight">${temNight}</span>-
                <span class="temDay">${temDay}</span>
                <span>℃</span>
              </div>
              <div class="wind">
                <span class="windDirection">${windDirection}</span>
                <span class="windPower">${windPower}</span>
              </div>
            </li>
          `,
          )
          .join('');
      });
    }
    js
    // 页面加载后默认展示北京市的天气信息
    // 北京市的城市 code 为 110100
    getWeather('110100');
    
    /**
     * 目标 2:搜索城市列表
     *  2.1 绑定 input 事件,获取关键字
     *  2.2 获取展示城市列表数据
     */
    document.querySelector('.search-city').addEventListener('input', (e) => {
      // console.log(e.target.value);
      myAxios({
        url: 'http://hmajax.itheima.net/api/weather/city',
        params: {
          city: e.target.value,
        },
      })
        .then((res) => {
          console.log(`${e.target.value}: ${JSON.stringify(res.data)}`);
          // 渲染城市列表
          document.querySelector('.search-list').innerHTML = res.data
            .map((item) => `<li class="city-item" data-code="${item.code}">${item.name}</li>`)
            .join('');
        })
        .catch((err) => {
          // console.dir 可以打印详细信息:对象的属性名
          console.dir(err.message);
        });
    });
    
    /**
     * 目标 3:切换城市天气
     *  3.1 绑定城市点击事件,获取城市 code 值
     *  3.2 调用获取并展示天气的函数
     */
    document.querySelector('.search-list').addEventListener('click', (e) => {
      console.log(e.target);
      if (e.target.classList.contains('city-item')) {
        // const cityCode = e.target.dataset.code;
        const cityCode = e.target.getAttribute('data-code');
        console.log(cityCode);
        getWeather(cityCode);
      }
    });

今日作业

客观题

在线答题:Day03_AJAX 原理

  1. 以下哪个方法可以把 JSON 字符串转 JS 数据类型( )?

    • A. Object.keys()
    • B. JSON.stringify()
    • C. JSON.parse()
    • D. Object.values()
    答案
    • 正确的选项是 C. JSON.parse()。这个方法用于将 JSON 字符串转换为相应的 JavaScript 对象。
    • A. Object.keys():返回一个给定对象的所有可枚举属性的字符串数组。
    • B. JSON.stringify():将 JavaScript 对象转换为 JSON 字符串。
    • D. Object.values():返回一个给定对象的所有可枚举属性值的数组。
  2. 以下哪个事件是在输入框失去焦点并内容改变是触发( )?

    • A. change
    • B. input
    • C. click
    • D. onchange
    答案
    • 正确的选项是 A. changechange 事件在用户对表单元素进行更改并提交更改时触发。在输入框中,它通常在输入框失去焦点后触发,但是具体的行为可能因浏览器而异。这个事件不同于 input 事件,input 事件在输入框的值发生任何变化时都会触发,不需要等到失去焦点。
    • 选项 B 中的 input 事件是在输入框的值发生变化时即时触发。
    • 选项 C 中的 click 事件是在用户点击元素时触发,与输入框的值变化和焦点失去无关。
    • 选项 D 中的 onchange 不是一个事件,而是一个事件处理属性的命名方式,通常用于绑定 change 事件的处理函数。
  3. 以下哪个是 AJAX 原理( )?

    • A. XMLHttpRequest
    • B. xmlhttprequest
    • C. axios
    • D. myAxios
    答案
    • 正确的选项是 A. XMLHttpRequest。AJAX(Asynchronous JavaScript and XML)是一种用于在不重新加载整个页面的情况下与服务器交换数据的技术。XMLHttpRequest 对象是实现这种技术的关键。它允许浏览器通过 JavaScript 发送 HTTP 请求,并处理服务器的响应。
    • B. xmlhttprequest:这是一个不正确的写法,正确的是 XMLHttpRequest
    • C. axiosaxios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。它是对 XMLHttpRequest 的封装,使得使用更加方便。
    • D. myAxios:这个选项是一个自定义的名称,与 AJAX 的原理直接无关。
  4. 原生 AJAX 如何携带查询参数( )?

    • A. 在 send 里发送
    • B. 使用 setRequestHeader 携带
    • C. 在 url 上自己按照格式拼接
    • D. 在 new 的时候携带
    答案
    • 正确的选项是 C. 在 url 上自己按照格式拼接。在原生的 AJAX 中,你可以将查询参数直接附加到请求的 URL 上。

      js
      var xhr = new XMLHttpRequest();
      var url = 'example.com/api/data?param1=value1&param2=value2';
      
      xhr.open('GET', url, true);
      xhr.send();
    • A. 在 send 里发送:这是不准确的,send 方法通常用于发送请求体(对于 POST 请求等),而不是查询参数。

    • B. 使用 setRequestHeader 携带:setRequestHeader 用于设置 HTTP 请求头,而不是用来携带查询参数。

    • D. 在 new 的时候携带:在 new XMLHttpRequest() 的时候并没有直接提供携带查询参数的接口,你需要手动将参数拼接到请求的 URL 上。

  5. 原生 AJAX 如何携带请求体参数()?

    • A. 在 new 的时候传递
    • B. 在 url 上携带
    • C. 在 send 方法中携带
    • D. 在 setRequestHeader 里携带
    答案
    • 正确的选项是 C. 在 send 方法中携带。对于包含请求体的 HTTP 请求(如 POST 请求),你可以在 send 方法中传递请求体参数。

      js
      var xhr = new XMLHttpRequest();
      const url = 'example.com/api/data';
      const params = new URLSearchParams({
        param1: 'value1',
        param2: 'value2',
      });
      
      xhr.open('POST', url);
      
      // 设置请求头(如果有需要)
      xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
      
      xhr.onreadystatechange = () => {
        if (xhr.readyState == 4 && xhr.status == 200) {
          // 请求成功的处理逻辑
          console.log(xhr.responseText);
        }
      };
      
      // 在 send 方法中携带请求体参数
      xhr.send(params);
    • A. 在 new 的时候传递:在 new XMLHttpRequest() 的时候通常用于配置一些基本的请求参数,但并不是用来携带请求体参数的地方。

    • B. 在 url 上携带:通常用于 GET 请求的查询参数,而不是请求体参数。

    • D. 在 setRequestHeader 里携带:setRequestHeader 用于设置请求头,而不是用来携带请求体参数。

  6. 什么是 Promise?

    • A. 管理异步任务,只能接收成功结果
    • B. 管理异步任务,只能接收失败结果
    • C. 只能管理同步任务,不能管理异步
    • D. 管理异步任务,在将来接收成功/失败状态及其结果值
    答案
    • 正确的选项是 D. 管理异步任务,在将来接收成功/失败状态及其结果值。Promise 是 JavaScript 中用于处理异步操作的对象。它代表了一个异步操作的最终完成或失败,以及其结果值。

    • Promise 对象有三种状态:

      • Pending(进行中): 初始状态,异步操作正在进行中。
      • Fulfilled(已成功): 异步操作成功完成,并返回一个结果值。
      • Rejected(已失败): 异步操作失败,并返回一个错误原因。
    • 通过 then 方法,你可以在 Promise 对象的状态发生变化时注册对应的处理函数,分别处理成功和失败的情况。

      js
      var promise = new Promise(function (resolve, reject) {
        // 异步操作,成功时调用 resolve,失败时调用 reject
        setTimeout(() => {
          resolve('成功结果');
          // 或者
          // reject("失败原因");
        }, 1000);
      });
      
      promise
        .then((result) => {
          // 处理成功的情况
          console.log(result);
        })
        .catch((error) => {
          // 处理失败的情况
          console.error(error);
        });
    • 这样,Promise 提供了一种更优雅和可读性更高的方式来处理异步代码。

  7. 以下哪个不是 Promise 的三种状态?

    • A. fulfilled
    • B. settled
    • C. resolve
    • D. pending
    答案
    • 正确的选项是 C. resolve。在 Promise 的状态中,fulfilled 表示已成功,rejected 表示已失败,而 pending 表示进行中。settled 这个术语通常用来描述 Promise 已经处于终态(fulfilledrejected)的状态,因此也是 Promise 的一种状态。
    • A. fulfilled: 已成功状态。
    • B. settled: 已终态,即已成功或已失败状态。
    • C. resolve: 不是状态,而是 Promise 的方法,用于将 Promise 从 pending 状态变为 fulfilled 状态。
    • D. pending: 进行中的状态。
  8. 以下哪个不是 axios 内部运作机制( )?

    • A. Promise
    • B. XHR
    • C. click
    • D. AJAX
    答案
    • 正确的选项是 C. clickclick 不是 axios 内部运作机制的一部分。
    • A. Promise: axios 使用 Promise 来处理异步操作。它支持 Promise 的链式调用,使得代码更加清晰和可读。
    • B. XHR(XMLHttpRequest): XMLHttpRequest 是在浏览器中实现 AJAX 的底层技术,而 axios 利用它来发起 HTTP 请求。
    • C. click: 不是 axios 的内部运作机制,而是一个事件。在前端开发中,click 通常用于处理用户点击事件。
    • D. AJAX: 虽然 axios 使用了类似 AJAX 的技术,但 AJAX 本身也不是 axios 的内部运作机制,而是表示 "Asynchronous JavaScript and XML",是一种用于在不重新加载整个页面的情况下与服务器交换数据的技术。
  9. 以下哪个会触发原生 XHR 请求和响应成功状态?

    • A. 200
    • B. 399
    • C. 190
    • D. 411
    答案
    • 正确的选项是 A. 200。HTTP 状态码 200 OK 表示服务器成功处理了请求。
    • B,C. 这不是标准的 HTTP 状态码。HTTP 状态码的范围是从 100 到 599。
    • D. 411: HTTP 状态码 411 Length Required 表示服务器拒绝在没有定义 Content-Length 头的情况下接受请求。这并不是成功状态,而是一个客户端错误状态。
  10. 以下哪个是监听 AJAX 响应完成事件?

    • A. onload
    • B. load
    • C. loadend
    • D. onready
    答案
    • 正确的选项是 C. loadendloadend 事件在 AJAX 请求完成(无论成功或失败)时触发。
    • A. onload: 这是一个事件处理属性,用于指定在 AJAX 请求成功完成时执行的函数。
    • B. load: 这是一个事件,但通常用于监听资源(如图片)的加载完成。在 XMLHttpRequest 上使用 onload 属性而不是 load 事件。
    • D. onready: 这不是标准的事件,没有与之对应的 AJAX 事件。正常的事件处理属性为 onloadonerror 等。

主观题

作业 1 - 英雄百科

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="stylesheet" href="./css/bootstrap.min.css" />
  <link rel="stylesheet" href="./css/index.css" />
  <title>英雄百科</title>
</head>

<body>
  <div class="main">
    <img class="cover"
      src="https://img.crawler.qq.com/lolwebschool/0/JAutoCMS_LOLWeb_f6416138ae858f73e2ca40a11587e17f/0"
      referrerpolicy="no-referrer" />
    <div class="hero-container">
      <input type="text" class="search" placeholder="检索" />
      <ul class="list">
        <li>
          <img src="http://game.gtimg.cn/images/lol/act/img/champion/Annie.png" alt="" referrerpolicy="no-referrer" />
          <p>安妮</p>
        </li>
      </ul>
    </div>
  </div>
  <div id="infoModal" class="modal" tabindex="-1">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">黑暗之女安妮</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <div class="info">
            <img src="http://game.gtimg.cn/images/lol/act/img/champion/Annie.png" class="icon img-thumbnail" alt="..."
              referrerpolicy="no-referrer" />
            <div class="progress-box">
              <div class="progress">
                <div class="attack progress-bar bg-success" style="width: 25%">攻击:</div>
              </div>
              <div class="progress">
                <div class="defense progress-bar bg-info" role="progressbar" style="width: 50%">防御:</div>
              </div>
              <div class="progress">
                <div class="magic progress-bar bg-warning" role="progressbar" style="width: 75%">魔法:</div>
              </div>
              <div class="progress">
                <div class="difficulty progress-bar bg-danger" role="progressbar" style="width: 100%">难度:</div>
              </div>
            </div>
          </div>
          <p>
            拥有危险夺命的能力,却长着一幅小大人儿的可爱模样,这就是掌握深不可测占火魔法的女孩——安妮。安妮生活在诺克萨斯北边的山脚下,但即便是在这种地方,她也依然是魔法师中的异类。她与火焰的紧密关系与生俱来——最初那些火焰是伴随着喜怒无常的冲动情绪出现的,后来她学会了如何掌握这些“好玩的小把戏”。其中,安妮最喜欢的就是她召唤亲爱的泰迪熊提伯斯——那头狂野的守护火兽。如今安妮已经迷失在了永恒的天真里,她在黑暗森林中游荡,寻觅着能陪自己玩耍的人。
          </p>
        </div>
      </div>
    </div>
  </div>
</body>
<script src="./js/axios.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<!-- 展示所有英雄列表数据 -->
<script src="./js/showHeroList.js"></script>
<!-- 搜索英雄 -->
<script src="./js/searchHero.js"></script>
<!-- 点击英雄查看英雄详情 -->
<script src="./js/showHeroInfo.js"></script>
<script>
</script>

</html>
js
/**
 * 展示所有英雄列表数据
 * api: https://hmajax.itheima.net/api/lol/search - GET
 */

const infoModal = document.querySelector('#infoModal');
const modal = new bootstrap.Modal(infoModal);

async function showHeroList() {
  try {
    const res = await axios.get('https://hmajax.itheima.net/api/lol/search');
    console.log(res.data.data);
    const heroList = res.data.data
      .map((item) => {
        return `
        <li>
          <img class="icon img-thumbnail" data-id='${item.heroId}' src="${item.icon}" alt="" referrerpolicy="no-referrer">
          <p>${item.title}</p>
        </li>
      `;
      })
      .join('');
    document.querySelector('.list').innerHTML = heroList;
  } catch (err) {
    console.dir(err);
    console.log(err.response);
  }
}

// 页面加载时展示所有英雄列表数据
showHeroList();
js
/**
 * 搜索英雄
 * api: https://hmajax.itheima.net/api/lol/search - GET
 * @param { String } q 搜索关键字
 */
async function searchHero(string) {
  try {
    const res = await axios.get(`https://hmajax.itheima.net/api/lol/search?q=${string}`);
    console.log(res.data.data);
    const heroList = res.data.data
      .map((item) => {
        return `
        <li>
          <img class="icon img-thumbnail" data-id='${item.heroId}' src="${item.icon}" alt="" referrerpolicy="no-referrer">
          <p>${item.title}</p>
        </li>
      `;
      })
      .join('');
    document.querySelector('.list').innerHTML = heroList;
  } catch (err) {
    console.dir(err);
    console.log(err.response);
  }
}

const searchInput = document.querySelector('input.search');
// 搜索框输入时实时搜索英雄
// searchInput.addEventListener('input', (e) => {

// onkeyup 事件会在键盘按键被松开时发生
searchInput.onkeyup = async (e) => {
  if (e.keyCode !== 13) return; // 没有按回车键就不执行
  const string = e.target.value.trim(); // 去除两端空格
  console.log(string);
  if (string) {
    searchHero(string);
  } else {
    console.log('输入为空,请输入搜索关键字!!!');
  }
};
js
/**
 * 点击英雄查看英雄详情
 * api: https://hmajax.itheima.net/api/lol/info - GET
 * @param { String } id 英雄 id
 */

async function showHeroInfo(id) {
  try {
    const res = await axios.get(`https://hmajax.itheima.net/api/lol/info?id=${id}`);
    const hero = res.data.data.hero;
    console.log(hero);
    // console.log(JSON.stringify(hero));
    const modalContent = document.querySelector('.modal-content');
    modalContent.querySelector('.modal-title').innerHTML = hero.name + hero.title;
    modalContent.querySelector('.icon').src = hero.icon;
    modalContent.querySelector('.attack').style.width = `${(hero.attack / 10) * 100}%`;
    modalContent.querySelector('.defense').style.width = `${(hero.defense / 10) * 100}%`;
    modalContent.querySelector('.magic').style.width = `${(hero.magic / 10) * 100}%`;
    modalContent.querySelector('.difficulty').style.width = `${(hero.difficulty / 10) * 100}%`;
    modalContent.querySelector('p').innerHTML = hero.shortBio;
    modal.show();
  } catch (err) {
    console.dir(err);
    console.log(err.response);
  }
}

document.querySelector('.list').addEventListener('click', async (e) => {
  const heroId = e.target.getAttribute('data-id');
  console.log(heroId);
  if (heroId) {
    showHeroInfo(heroId);
  }
});

排错题

排错题 - 天气预报
html
<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="./css/reset.css">
  <link rel="stylesheet" href="./css/index.css">
  <title>案例_天气预报</title>
</head>

<body>
  <div class="container">
    <!-- 顶部 -->
    <div class="top-box">
      <div class="title">
        <span class="dateShort">10 月 28 日</span>
        <span class="calendar">农历&nbsp;
          <span class="dateLunar">十月初四</span>
        </span>
      </div>
      <div class="search-box">
        <div class="location">
          <img src="./imgs/定位.png" alt="">
          <span class="area">城市名</span>
        </div>
        <!-- 搜索框 + 搜索列表 -->
        <div class="search">
          <input type="text" class="search-city" placeholder="搜索城市">
          <ul class="search-list">
            <li class="city-item">北京市</li>
          </ul>
        </div>
      </div>
    </div>
    <!-- 当前天气 -->
    <div class="weather-box">
      <div class="tem-box">
        <span class="temp">
          <span class="temperature">12</span>
          <span>°</span>
        </span>
      </div>
      <div class="climate-box">
        <div class="air">
          <span class="psPm25">55</span>
          <span class="psPm25Level">良</span>
        </div>
        <ul class="weather-list">
          <li>
            <img src="./imgs/小雨-line.png" class="weatherImg" alt="">
            <span class="weather">多云</span>
          </li>
          <li class="windDirection">东南风</li>
          <li class="windPower">2 级</li>
        </ul>
      </div>
    </div>
    <div class="today-weather">
      <div class="range-box">
        <span>今天:</span>
        <span class="range">
          <span class="weather">晴</span>
          <span class="temNight">9</span>
          <span>-</span>
          <span class="temDay">14</span>
          <span>℃</span>
        </span>
      </div>
      <ul class="sun-list">
        <li>
          <span>紫外线</span>
          <span class="ultraviolet">强</span>
        </li>
        <li>
          <span>湿度</span>
          <span class="humidity">53</span>%
        </li>
        <li>
          <span>日出</span>
          <span class="sunriseTime">06:38</span>
        </li>
        <li>
          <span>日落</span>
          <span class="sunsetTime">17:18</span>
        </li>
      </ul>
    </div>
    <!-- 周天气预报 -->
    <div class="week-weather-box">
      <div class="title">7 日内天气预报</div>
      <ul class="week-wrap">
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temNight">12</span>-
            <span class="temDay">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
      </ul>
    </div>
  </div>
  <!-- 自己封装 myAxios 函数 -->
  <script src="./js/my-axios.js"></script>
  <!-- 搜索框 + 下拉菜单出现逻辑 -->
  <script src="./js/search.js"></script>
  <!-- 核心 js -->
  <script src="./js/index.js"></script>
</body>

</html>
js
/**
 * 目标 1:默认显示 - 北京市天气
 *  1.1 获取北京市天气数据
 *  1.2 数据展示到页面
 */
// 获取并渲染城市天气函数
function getWeather(cityCode) {
  // 1.1 获取北京市天气数据
  myAxios({
    url: 'http://hmajax.itheima.net/api/waether',
    params: {
      city: cityCode,
    },
  }).then((result) => {
    console.log(result);
    const wObj = result.data;
    // 1.2 数据展示到页面
    // 阳历和农历日期
    const dateStr = `<span class="dateShort">${wObj.date}</span>
    <span class="calendar">农历&nbsp;
      <span class="dateLunar">${wObj.dateLunar}</span>
    </span>`;
    document.querySelector('.title').innerHTML = dateStr;
    // 城市名字
    document.querySelector('.area').innerHTML = wObj.area;
    // 当天气温
    const nowWStr = `<div class="tem-box">
    <span class="temp">
      <span class="temperature">${wObj.temperature}</span>
      <span>°</span>
    </span>
  </div>
  <div class="climate-box">
    <div class="air">
      <span class="psPm25">${wObj.psPm25}</span>
      <span class="psPm25Level">${wObj.psPm25Level}</span>
    </div>
    <ul class="weather-list">
      <li>
        <img src="${wObj.weatherImg}" class="weatherImg" alt="">
        <span class="weather">${wObj.weather}</span>
      </li>
      <li class="windDirection">${wObj.windDirection}</li>
      <li class="windPower">${wObj.windPower}</li>
    </ul>
  </div>`;
    document.querySelector('.weather-box').innerHTML = nowWStr;
    // 当天天气
    const twObj = wObj.todayWeather;
    const todayWStr = `<div class="range-box">
    <span>今天:</span>
    <span class="range">
      <span class="weather">${twObj.weather}</span>
      <span class="temNight">${twObj.temNight}</span>
      <span>-</span>
      <span class="temDay">${twObj.temDay}</span>
      <span>℃</span>
    </span>
  </div>
  <ul class="sun-list">
    <li>
      <span>紫外线</span>
      <span class="ultraviolet">${twObj.ultraviolet}</span>
    </li>
    <li>
      <span>湿度</span>
      <span class="humidity">${twObj.humidity}</span>%
    </li>
    <li>
      <span>日出</span>
      <span class="sunriseTime">${twObj.sunriseTime}</span>
    </li>
    <li>
      <span>日落</span>
      <span class="sunsetTime">${twObj.sunsetTime}</span>
    </li>
  </ul>`;
    document.querySelector('.today-weather').innerHTML = todayWStr;

    // 7 日天气预报数据展示
    const dayForecast = wObj.dayForecast;
    const dayForecastStr = dayForecast
      .map((item) => {
        return `<li class="item">
      <div class="date-box">
        <span class="dateFormat">${item.dateFormat}</span>
        <span class="date">${item.date}</span>
      </div>
      <img src="${item.weatherImg}" alt="" class="weatherImg">
      <span class="weather">${item.weather}</span>
      <div class="temp">
        <span class="temNight">${item.temNight}</span>-
        <span class="temDay">${item.temDay}</span>
        <span>℃</span>
      </div>
      <div class="wind">
        <span class="windDirection">${item.windDirection}</span>
        <span class="windPower">${item.windPower}</span>
      </div>
    </li>`;
      })
      .join('');
    // console.log(dayForecastStr)
    document.querySelector('.week-wrap').innerHTML = dayForecastStr;
  });
}

// 默认进入网页 - 就要获取天气数据(北京市城市编码:'110100')
getWeather('1101001');

/**
 * 目标 2:搜索城市列表
 *  2.1 绑定 input 事件,获取关键字
 *  2.2 获取展示城市列表数据
 */
// 2.1 绑定 input 事件,获取关键字
document.querySelector('.search-city').addEventListener('input', (e) => {
  console.log(e.target.value);
  // 2.2 获取展示城市列表数据
  myAxios({
    url: 'http://hmajax.itheima.net/api/weather/city',
    params: {
      city: e.target.value,
    },
  }).then((result) => {
    console.log(result);
    const liStr = result.data
      .map((item) => {
        return `<li class="city-item" data-code="${item.code}">${item.name}</li>`;
      })
      .join('');
    console.log(liStr);
    document.querySelector('.search-list').innerHTML = liStr;
  });
});

/**
 * 目标 3:切换城市天气
 *  3.1 绑定城市点击事件,获取城市 code 值
 *  3.2 调用获取并展示天气的函数
 */
// 3.1 绑定城市点击事件,获取城市 code 值
document.querySelector('.city-item').addEventListener('click', (e) => {
  if (e.target.classList.contains('city-item')) {
    // 只有点击城市 li 才会走这里
    const cityCode = e.target.dataset.code;
    console.log(cityCode);
    // 3.2 调用获取并展示天气的函数
    getWeather(cityCode);
  }
});
  1. 打开控制台提示 api url 报错:

    js
    hmajax.itheima.net/api/waether?city=1101001:1     Failed to load resource: the server responded with a status of 404 (Not Found)
    my-axios.js:14 Uncaught (in promise) Error: <h1>404 Not Found</h1>
        at XMLHttpRequest.<anonymous> (my-axios.js:14:16)

    查看 api 后发现代码里的 api 写为

    js
      myAxios({
        url: 'http://hmajax.itheima.net/api/waether',
        params: {

    应该为

    js
    myAxios({
        url: 'http://hmajax.itheima.net/api/weather',
        params: {
  2. 再次打开控制台还是发现有错,但是这次是查询参数错误:

    js
    my-axios.js:22     GET http://hmajax.itheima.net/api/weather?city=1101001 400 (Bad Request)
    my-axios.js:14 Uncaught (in promise) Error: {"code":400001,"message":"入参城市 code:1101001 错误","data":{}}
        at XMLHttpRequest.<anonymous> (my-axios.js:14:16)

    查看 api 后发现北京市的城市 code 为 110100,代码里为 1101001

  3. 再次打开控制台,这次没有报错。但是搜索框中输入城市后选择城市,但是天气和城市名都没有变。查看代码:

    js
    // 3.1 绑定城市点击事件,获取城市 code 值
    document.querySelector('.city-item').addEventListener('click', (e) => {
      if (e.target.classList.contains('city-item')) {

    列表是动态创建的,而绑定的 JS 代码上来就执行完了,这个时候列表数据还未回来,标签没有,获取不到标签,所以绑定不上事件!但是呢,数据回来后,列表标签创建插入到页面上,实际是有标签在页面,但是事件绑定的代码没有在创建后执行

    发现点击事件绑定有问题,动态创建的标签绑定事件要用 事件委托 + 判断。这里应该给搜索框绑定点击事件。

    js
    // 3.1 绑定城市点击事件,获取城市 code 值
    document.querySelector('.search-list').addEventListener('click', (e) => {
      if (e.target.classList.contains('city-item')) {
排错题 - 天气预报 (修正)
html
<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="./css/reset.css">
  <link rel="stylesheet" href="./css/index.css">
  <title>案例_天气预报</title>
</head>

<body>
  <div class="container">
    <!-- 顶部 -->
    <div class="top-box">
      <div class="title">
        <span class="dateShort">10 月 28 日</span>
        <span class="calendar">农历&nbsp;
          <span class="dateLunar">十月初四</span>
        </span>
      </div>
      <div class="search-box">
        <div class="location">
          <img src="./imgs/定位.png" alt="">
          <span class="area">城市名</span>
        </div>
        <!-- 搜索框 + 搜索列表 -->
        <div class="search">
          <input type="text" class="search-city" placeholder="搜索城市">
          <ul class="search-list">
            <li class="city-item">北京市</li>
          </ul>
        </div>
      </div>
    </div>
    <!-- 当前天气 -->
    <div class="weather-box">
      <div class="tem-box">
        <span class="temp">
          <span class="temperature">12</span>
          <span>°</span>
        </span>
      </div>
      <div class="climate-box">
        <div class="air">
          <span class="psPm25">55</span>
          <span class="psPm25Level">良</span>
        </div>
        <ul class="weather-list">
          <li>
            <img src="./imgs/小雨-line.png" class="weatherImg" alt="">
            <span class="weather">多云</span>
          </li>
          <li class="windDirection">东南风</li>
          <li class="windPower">2 级</li>
        </ul>
      </div>
    </div>
    <div class="today-weather">
      <div class="range-box">
        <span>今天:</span>
        <span class="range">
          <span class="weather">晴</span>
          <span class="temNight">9</span>
          <span>-</span>
          <span class="temDay">14</span>
          <span>℃</span>
        </span>
      </div>
      <ul class="sun-list">
        <li>
          <span>紫外线</span>
          <span class="ultraviolet">强</span>
        </li>
        <li>
          <span>湿度</span>
          <span class="humidity">53</span>%
        </li>
        <li>
          <span>日出</span>
          <span class="sunriseTime">06:38</span>
        </li>
        <li>
          <span>日落</span>
          <span class="sunsetTime">17:18</span>
        </li>
      </ul>
    </div>
    <!-- 周天气预报 -->
    <div class="week-weather-box">
      <div class="title">7 日内天气预报</div>
      <ul class="week-wrap">
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temNight">12</span>-
            <span class="temDay">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
        <li class="item">
          <div class="date-box">
            <span class="dateFormat">今天</span>
            <span class="date">10 月 28 日</span>
          </div>
          <img src="./imgs/多云.png" alt="" class="weatherImg">
          <span class="weather">多云</span>
          <div class="temp">
            <span class="temDay">12</span>-
            <span class="temNight">12</span>
            <span>℃</span>
          </div>
          <div class="wind">
            <span class="windDirection">东南风</span>
            <span class="windPower">&lt;3 级</span>
          </div>
        </li>
      </ul>
    </div>
  </div>
  <!-- 自己封装 myAxios 函数 -->
  <script src="./js/my-axios.js"></script>
  <!-- 搜索框 + 下拉菜单出现逻辑 -->
  <script src="./js/search.js"></script>
  <!-- 核心 js -->
  <script src="./js/index.js"></script>
</body>

</html>
js
/**
 * 目标 1:默认显示 - 北京市天气
 *  1.1 获取北京市天气数据
 *  1.2 数据展示到页面
 */
// 获取并渲染城市天气函数
function getWeather(cityCode) {
  // 1.1 获取北京市天气数据
  myAxios({
    url: 'http://hmajax.itheima.net/api/waether',
    params: {
      city: cityCode,
    },
  }).then((result) => {
    console.log(result);
    const wObj = result.data;
    // 1.2 数据展示到页面
    // 阳历和农历日期
    const dateStr = `<span class="dateShort">${wObj.date}</span>
    <span class="calendar">农历&nbsp;
      <span class="dateLunar">${wObj.dateLunar}</span>
    </span>`;
    document.querySelector('.title').innerHTML = dateStr;
    // 城市名字
    document.querySelector('.area').innerHTML = wObj.area;
    // 当天气温
    const nowWStr = `<div class="tem-box">
    <span class="temp">
      <span class="temperature">${wObj.temperature}</span>
      <span>°</span>
    </span>
  </div>
  <div class="climate-box">
    <div class="air">
      <span class="psPm25">${wObj.psPm25}</span>
      <span class="psPm25Level">${wObj.psPm25Level}</span>
    </div>
    <ul class="weather-list">
      <li>
        <img src="${wObj.weatherImg}" class="weatherImg" alt="">
        <span class="weather">${wObj.weather}</span>
      </li>
      <li class="windDirection">${wObj.windDirection}</li>
      <li class="windPower">${wObj.windPower}</li>
    </ul>
  </div>`;
    document.querySelector('.weather-box').innerHTML = nowWStr;
    // 当天天气
    const twObj = wObj.todayWeather;
    const todayWStr = `<div class="range-box">
    <span>今天:</span>
    <span class="range">
      <span class="weather">${twObj.weather}</span>
      <span class="temNight">${twObj.temNight}</span>
      <span>-</span>
      <span class="temDay">${twObj.temDay}</span>
      <span>℃</span>
    </span>
  </div>
  <ul class="sun-list">
    <li>
      <span>紫外线</span>
      <span class="ultraviolet">${twObj.ultraviolet}</span>
    </li>
    <li>
      <span>湿度</span>
      <span class="humidity">${twObj.humidity}</span>%
    </li>
    <li>
      <span>日出</span>
      <span class="sunriseTime">${twObj.sunriseTime}</span>
    </li>
    <li>
      <span>日落</span>
      <span class="sunsetTime">${twObj.sunsetTime}</span>
    </li>
  </ul>`;
    document.querySelector('.today-weather').innerHTML = todayWStr;

    // 7 日天气预报数据展示
    const dayForecast = wObj.dayForecast;
    const dayForecastStr = dayForecast
      .map((item) => {
        return `<li class="item">
      <div class="date-box">
        <span class="dateFormat">${item.dateFormat}</span>
        <span class="date">${item.date}</span>
      </div>
      <img src="${item.weatherImg}" alt="" class="weatherImg">
      <span class="weather">${item.weather}</span>
      <div class="temp">
        <span class="temNight">${item.temNight}</span>-
        <span class="temDay">${item.temDay}</span>
        <span>℃</span>
      </div>
      <div class="wind">
        <span class="windDirection">${item.windDirection}</span>
        <span class="windPower">${item.windPower}</span>
      </div>
    </li>`;
      })
      .join('');
    // console.log(dayForecastStr)
    document.querySelector('.week-wrap').innerHTML = dayForecastStr;
  });
}

// 默认进入网页 - 就要获取天气数据(北京市城市编码:'110100')
getWeather('1101001');

/**
 * 目标 2:搜索城市列表
 *  2.1 绑定 input 事件,获取关键字
 *  2.2 获取展示城市列表数据
 */
// 2.1 绑定 input 事件,获取关键字
document.querySelector('.search-city').addEventListener('input', (e) => {
  console.log(e.target.value);
  // 2.2 获取展示城市列表数据
  myAxios({
    url: 'http://hmajax.itheima.net/api/weather/city',
    params: {
      city: e.target.value,
    },
  }).then((result) => {
    console.log(result);
    const liStr = result.data
      .map((item) => {
        return `<li class="city-item" data-code="${item.code}">${item.name}</li>`;
      })
      .join('');
    console.log(liStr);
    document.querySelector('.search-list').innerHTML = liStr;
  });
});

/**
 * 目标 3:切换城市天气
 *  3.1 绑定城市点击事件,获取城市 code 值
 *  3.2 调用获取并展示天气的函数
 */
// 3.1 绑定城市点击事件,获取城市 code 值
document.querySelector('.city-item').addEventListener('click', (e) => {
  if (e.target.classList.contains('city-item')) {
    // 只有点击城市 li 才会走这里
    const cityCode = e.target.dataset.code;
    console.log(cityCode);
    // 3.2 调用获取并展示天气的函数
    getWeather(cityCode);
  }
});
js
/**
 * 目标 1:默认显示 - 北京市天气
 *  1.1 获取北京市天气数据
 *  1.2 数据展示到页面
 */
// 获取并渲染城市天气函数
function getWeather(cityCode) {
  // 1.1 获取北京市天气数据
  myAxios({
    url: 'http://hmajax.itheima.net/api/weather',
    params: {
      city: cityCode,
    },
  }).then((result) => {
    console.log(result);
    const wObj = result.data;
    // 1.2 数据展示到页面
    // 阳历和农历日期
    const dateStr = `<span class="dateShort">${wObj.date}</span>
    <span class="calendar">农历&nbsp;
      <span class="dateLunar">${wObj.dateLunar}</span>
    </span>`;
    document.querySelector('.title').innerHTML = dateStr;
    // 城市名字
    document.querySelector('.area').innerHTML = wObj.area;
    // 当天气温
    const nowWStr = `<div class="tem-box">
    <span class="temp">
      <span class="temperature">${wObj.temperature}</span>
      <span>°</span>
    </span>
  </div>
  <div class="climate-box">
    <div class="air">
      <span class="psPm25">${wObj.psPm25}</span>
      <span class="psPm25Level">${wObj.psPm25Level}</span>
    </div>
    <ul class="weather-list">
      <li>
        <img src="${wObj.weatherImg}" class="weatherImg" alt="">
        <span class="weather">${wObj.weather}</span>
      </li>
      <li class="windDirection">${wObj.windDirection}</li>
      <li class="windPower">${wObj.windPower}</li>
    </ul>
  </div>`;
    document.querySelector('.weather-box').innerHTML = nowWStr;
    // 当天天气
    const twObj = wObj.todayWeather;
    const todayWStr = `<div class="range-box">
    <span>今天:</span>
    <span class="range">
      <span class="weather">${twObj.weather}</span>
      <span class="temNight">${twObj.temNight}</span>
      <span>-</span>
      <span class="temDay">${twObj.temDay}</span>
      <span>℃</span>
    </span>
  </div>
  <ul class="sun-list">
    <li>
      <span>紫外线</span>
      <span class="ultraviolet">${twObj.ultraviolet}</span>
    </li>
    <li>
      <span>湿度</span>
      <span class="humidity">${twObj.humidity}</span>%
    </li>
    <li>
      <span>日出</span>
      <span class="sunriseTime">${twObj.sunriseTime}</span>
    </li>
    <li>
      <span>日落</span>
      <span class="sunsetTime">${twObj.sunsetTime}</span>
    </li>
  </ul>`;
    document.querySelector('.today-weather').innerHTML = todayWStr;

    // 7 日天气预报数据展示
    const dayForecast = wObj.dayForecast;
    const dayForecastStr = dayForecast
      .map((item) => {
        return `<li class="item">
      <div class="date-box">
        <span class="dateFormat">${item.dateFormat}</span>
        <span class="date">${item.date}</span>
      </div>
      <img src="${item.weatherImg}" alt="" class="weatherImg">
      <span class="weather">${item.weather}</span>
      <div class="temp">
        <span class="temNight">${item.temNight}</span>-
        <span class="temDay">${item.temDay}</span>
        <span>℃</span>
      </div>
      <div class="wind">
        <span class="windDirection">${item.windDirection}</span>
        <span class="windPower">${item.windPower}</span>
      </div>
    </li>`;
      })
      .join('');
    // console.log(dayForecastStr)
    document.querySelector('.week-wrap').innerHTML = dayForecastStr;
  });
}

// 默认进入网页 - 就要获取天气数据(北京市城市编码:'110100')
getWeather('110100');

/**
 * 目标 2:搜索城市列表
 *  2.1 绑定 input 事件,获取关键字
 *  2.2 获取展示城市列表数据
 */
// 2.1 绑定 input 事件,获取关键字
document.querySelector('.search-city').addEventListener('input', (e) => {
  console.log(e.target.value);
  // 2.2 获取展示城市列表数据
  myAxios({
    url: 'http://hmajax.itheima.net/api/weather/city',
    params: {
      city: e.target.value,
    },
  }).then((result) => {
    console.log(result);
    const liStr = result.data
      .map((item) => {
        return `<li class="city-item" data-code="${item.code}">${item.name}</li>`;
      })
      .join('');
    console.log(liStr);
    document.querySelector('.search-list').innerHTML = liStr;
  });
});

/**
 * 目标 3:切换城市天气
 *  3.1 绑定城市点击事件,获取城市 code 值
 *  3.2 调用获取并展示天气的函数
 */
// 3.1 绑定城市点击事件,获取城市 code 值
document.querySelector('.search-list').addEventListener('click', (e) => {
  if (e.target.classList.contains('city-item')) {
    // 只有点击城市 li 才会走这里
    const cityCode = e.target.dataset.code;
    console.log(cityCode);
    // 3.2 调用获取并展示天气的函数
    getWeather(cityCode);
  }
});

面试题

  1. 你是怎么理解 Promise ?

    参考文档

参考文档

  1. Ajax 原生-mdn
  2. 同步异步-mdn
  3. 回调函数-mdn
  4. Promise-mdn